/** @file b_sorter.cpp
	@brief Geometry batch sorter */

#include "render/batch/b_sorter.h"

using namespace G3;

//==============================================================================
// Global variables
//==============================================================================
G3::BATCH_SORTER G3::MainBatchSorter;

//==============================================================================
// Sorting functor
//==============================================================================
bool G3::BATCH_SORT_FUNCTOR::operator() (G3::BATCH *a, G3::BATCH *b) const {
    if (a != NULL && b != NULL)
        return (a->key < b->key);
    return false;
}

//==============================================================================
// Constructors
//==============================================================================
G3::BATCH_SORTER::BATCH_SORTER () {
    sorted = false;
    online = false;
    thread = NULL;

    log = &G3::MainLog;
}

//==============================================================================
// Destructors
//==============================================================================
G3::BATCH_SORTER::~BATCH_SORTER () {
    clear ();
}

//==============================================================================
// Sorter thread functor
//==============================================================================
void G3::BATCH_SORTER_THREAD::operator()() {
    if (MainBatchSorter.log)
        MainBatchSorter.log->Report ("Batch sorting thread online..\n");

    MainBatchSorter.online = true;

    while (MainBatchSorter.online) {
        // Idle if there's nothing to update
        if (!MainBatchSorter.sorted) {
            MainBatchSorter.update_add ();
            MainBatchSorter.update_remove ();

//            printf ("Batches: %d\n", MainBatchSorter.batches.size ());
        }

        // Idle for some time
        boost::thread::yield ();
    }
}

//==============================================================================
// Starts the sorter thread
//==============================================================================
void G3::BATCH_SORTER::start () {
    if (log)
        log->Report ("BATCH_SORTER::start: Starting the batch sorter thread..\n");

    static BATCH_SORTER_THREAD sortt;

    try {
        thread = new boost::thread (sortt);
    } catch (...) {
        if (log)
            log->Report ("BATCH_SORTER::start: Failed to start the thread..\n");
    }
}

//==============================================================================
// Stops the sorter thread
//==============================================================================
void G3::BATCH_SORTER::stop () {
    online = false;

    if (thread != NULL) {
        if (log)
            log->Report ("BATCH_SORTER::stop: Joining the batch sorter thread..\n");

        try {
            thread->join ();
            delete thread;
        } catch (...) {
            if (log)
                log->Report ("BATCH_SORTER::stop: Failed to join the thread..\n");
        }

        thread = NULL;
    }
}

//==============================================================================
// Checks if the sorter is online
//==============================================================================
bool G3::BATCH_SORTER::isOnline () {
    return online;
}

//==============================================================================
// Adds a new batch into the list
//==============================================================================
bool G3::BATCH_SORTER::add (G3::BATCH *batch) {
    if (batch == NULL) {
        if (log)
            log->Report ("BATCH_SORTER::add: Trying to add a NULL pointer\n");
        return false;
    }

    // Add the batch into the pending batches list
    mutex_add.lock ();
    pending_add.insert (batch);
    mutex_add.unlock ();

    sorted = false;

    return true;
}

//==============================================================================
// Removes a batch from the list
//==============================================================================
bool G3::BATCH_SORTER::remove (G3::BATCH *batch) {
    if (batch == NULL) {
        if (log)
            log->Report ("BATCH_SORTER::remove: Trying to remove a NULL pointer\n");
        return false;
    }

    // Add the batch into the pending remove batches list
    mutex_remove.lock ();
    pending_remove.insert (batch);
    mutex_remove.unlock ();

    sorted = false;

    return true;
}

//==============================================================================
// Dumps the batch sorter
//==============================================================================
void G3::BATCH_SORTER::dump () {
    std::deque<BATCH *>::iterator it;
    std::set<BATCH *>::iterator it2;
    std::string batch_key;
    int id = 0;

    if (log) {
        log->Report ("Dumping the batch sorter:\n");
        log->Report (" Online: %d\n", online);
        log->Report (" Sorted: %d\n", sorted);
        log->Report (" Batches (%d):\n", batches.size ());

        it = batches.begin ();
        while (it != batches.end ()) {
            log->Report ("  Batch %d: 0x%X: ", id, (*it));

            if (*it != NULL) {
                batch_key = (*it)->keyToString ();
            } else {
                batch_key = "(null)";
            }

            log->Report ("%s\n", batch_key.c_str ());

            it++;
            id++;
        }

        id = 0;

        log->Report (" Pending (Add %d):\n", pending_add.size ());

        it2 = pending_add.begin ();
        while (it2 != pending_add.end ()) {
            log->Report ("  Batch %d: 0x%X: ", id, (*it2));

            if (*it2 != NULL) {
                batch_key = (*it2)->keyToString ();
            } else {
                batch_key = "(null)";
            }

            log->Report ("%s\n", batch_key.c_str ());

            it2++;
            id++;
        }

        id = 0;

        log->Report (" Pending (Remove %d):\n", pending_remove.size ());

        it2 = pending_remove.begin ();
        while (it2 != pending_remove.end ()) {
            log->Report ("  Batch %d: 0x%X: ", id, (*it2));

            if (*it2 != NULL) {
                batch_key = (*it2)->keyToString ();
            } else {
                batch_key = "(null)";
            }

            log->Report ("%s\n", batch_key.c_str ());

            it2++;
            id++;
        }
    }
}

//==============================================================================
// Clears the list of batches
//==============================================================================
void G3::BATCH_SORTER::clear () {
    stop ();

    if (!batches.empty ())
        batches.clear ();
    if (!pending_add.empty ())
        pending_add.clear ();
    if (!pending_remove.empty ())
        pending_remove.clear ();
}

//==============================================================================
// Updates the list of batches by adding the pending batches
//==============================================================================
void G3::BATCH_SORTER::update_add () {
    if (pending_add.empty ())
        return;

    std::set<BATCH *>::iterator it;
    BATCH_SORT_FUNCTOR sorter;
    bool changes = false;

    // Add pending batches
//    printf ("Adding pending %d batches..\n", pending_add.size ());
//    Assert (false, "Breakpoint");
//        if (log)
//            log->Report ("Adding pending %d batches..\n", pending_add.size ());

    mutex_add.lock ();

    it = pending_add.begin ();
    while (it != pending_add.end ()) {
        if (*it == NULL) {
            it++;
            continue;
        }

//        printf ("Batch 0x%X %s\n", (uint) *it, (*it)->keyToString ().c_str ());

        // Make sure that it doesn't already exist
        if (batches.empty () || !std::binary_search (batches.begin (), batches.end (), *it, sorter)) {
//            printf ("Adding %s\n", (*it)->keyToString ().c_str ());
//                if (log)
//                    log->Report (" Adding %s\n", (*it)->keyToString ().c_str ());

            mutex_batches.lock ();
            batches.push_back (*it);
            mutex_batches.unlock ();

            changes = true;
        }

        it++;
    }

    pending_add.clear ();

    mutex_add.unlock ();

//        if (log)
//            log->Report ("Pending batches added..\n");

    // No changes ? There's no need for sorting it then..
    if (!changes) {
        sorted = true;

//        if (log)
//            log->Report ("No changes..\n");

        return;
    }

    sort ();
}

//==============================================================================
// Updates the list of batches by removing the pending batches
//==============================================================================
void G3::BATCH_SORTER::update_remove () {
    if (batches.empty () && pending_remove.empty ())
        return;

    std::set<BATCH *>::iterator it;
    std::deque<BATCH *>::iterator it2;
    bool changes = false;

    // Remove pending batches - takes a lot of time
    if (!pending_remove.empty ()) {
//        printf ("Removing pending %d batches..\n", pending_remove.size ());
//        if (log)
//            log->Report ("Removing %d pending batches..\n", pending_remove.size ());

        mutex_remove.lock ();

        // Is there anything to remove at all ?
        if (!batches.empty ()) {
            it = pending_remove.begin ();

            while (it != pending_remove.end ()) {
                // Find the batch
                //! \todo Optimize
                it2 = std::find (batches.begin (), batches.end (), *it);

                // Found it ?
                if (it2 != batches.end () && *it2 == *it) {
//                    printf ("Removing %s\n", (*it)->keyToString ().c_str ());
//                    if (log)
//                        log->Report (" Removing %s\n", (*it)->keyToString ().c_str ());

                    mutex_batches.lock ();
                    batches.erase (it2);
                    mutex_batches.unlock ();

                    changes = true;
                }

                it++;
            }
        }

        pending_remove.clear ();

        mutex_remove.unlock ();

//        if (log)
//            log->Report ("Pending batches removed..\n");
    }

    // No changes ? There's no need for sorting it then..
    if (!changes) {
        sorted = true;

//        if (log)
//            log->Report ("No changes..\n");

        return;
    }

    sort ();
}

void G3::BATCH_SORTER::sort () {
    BATCH_SORT_FUNCTOR sorter;

//    if (log)
//        log->Report ("Sorting..\n");

    // Sort them - may take a long time - that's the reason for the pending lists
    mutex_batches.lock ();
    std::sort (batches.begin (), batches.end (), sorter);
    mutex_batches.unlock ();

//    if (log)
//        log->Report ("Sorted..\n");

    sorted = true;
}

